#include "stdafx.h"
#include "../Lexer.h"
#include "../String.h"
#include "../Context.h"

namespace Gs
{
	// Defines the operator tokens.
	TokenDefinition operators[] =
	{
		{ "(", Token::LeftBracket },
		{ ")", Token::RightBracket },
		{ "{", Token::LeftBrace },
		{ "}", Token::RightBrace },
		{ ",", Token::Comma },
		{ ":", Token::Colon },
		{ ";", Token::SemiColon },
		{ "==", Token::Equality },
		{ "=", Token::Assign },
		{ ".", Token::Dot },
		{ "*=", Token::MultiplyAssign },
		{ "*", Token::Multiply },
		{ "--", Token::Decrement },
		{ "-=", Token::MinusAssign },
		{ "-", Token::Minus },
		{ "++", Token::Increment },
		{ "+=", Token::PlusAssign },
		{ "+", Token::Plus },
		{ "/=", Token::DivideAssign },
		{ "/", Token::Divide },
		{ "%=", Token::ModulusAssign },
		{ "%", Token::Modulus },
		{ "~=", Token::OnesComplementAssign },
		{ "~", Token::OnesComplement },
		{ "?", Token::Question },
		{ "!=", Token::NotEqual },
		{ "!", Token::Not },
		{ "<<=", Token::BitwiseLeftShiftAssign },
		{ "<<", Token::BitwiseLeftShift },
		{ "<=", Token::LessThanOrEqual },
		{ "<", Token::LessThan },
		{ ">>=", Token::BitwiseRightShiftAssign },
		{ ">>", Token::BitwiseRightShift },
		{ ">=", Token::MoreThanOrEqual },
		{ ">", Token::MoreThan },
		{ "||", Token::Or },
		{ "|=", Token::BitwiseOrAssign },
		{ "|", Token::BitwiseOr },
		{ "&&", Token::And },
		{ "&=", Token::BitwiseAndAssign },
		{ "&", Token::BitwiseAnd },
		{ "^=", Token::BitwiseXorAssign },
		{ "^", Token::BitwiseXor }
	};
	
	// Defines the keyword tokens.
	TokenDefinition keywords[] =
	{
		{ "if", Token::If },
		{ "else", Token::Else },
		{ "for", Token::For },
		{ "foreach", Token::ForEach },
		{ "do", Token::Do },
		{ "while", Token::While },
		{ "to", Token::To },
		{ "string", Token::String },
		{ "int", Token::Int },
		{ "in", Token::In },
		{ "float", Token::Float },
		{ "function", Token::Function },
		{ "class", Token::Class },
		{ "var", Token::Var },
		{ "global", Token::Global },
		{ "end", Token::End },
		{ "true", Token::True },
		{ "false", Token::False }
	};

	// Determines if the character is an alphabetical.
	inline bool IsAlpha(char c)
	{
		return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
	}

	// Determines if the character is numeric.
	inline bool IsNumeric(char c)
	{
		return c >= '0' && c <= '9';
	}

	// Determines if the character is alpha-numeric.
	inline bool IsAlphaNumeric(char c)
	{
		return IsAlpha(c) || IsNumeric(c);
	}

	// Determines if the character is an identifier character.
	inline bool IsIdentifierChar(char c)
	{
		return IsAlphaNumeric(c) || c == '_';
	}
	
	// Determines if the character is the start of an identifier.
	inline bool IsStartOfIdentifier(char c)
	{
		return IsAlpha(c) || c == '_';
	}

	// Determines if the character is a hexadecimal character.
	inline bool IsHexadecimal(char c)
	{
		return IsNumeric(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
	}

	// Converts the token to a string.
	String *Token::ToString(Lexer *lexer)
	{
		Gs::String *s = new Gs::String(length + 1);
		strncpy((char *)s->GetBuffer(), lexer->GetScript() + start, length);
		s->GetBuffer()[length] = 0;
		return s;
	}

	// Converts the token to a string.
	const char *Token::ToString(Lexer *lexer, char *to)
	{
		strncpy(to, lexer->GetScript() + start, length);
		to[length] = 0;
		return to;
	}

	// Initialises a new instance of the Lexer class.
	Lexer::Lexer(Context *context, const char *script, const char *source)
		: _context(0), _first(0), _last(0), _script(script), _source(source)
	{
	}

	// Destroys the instance.
	Lexer::~Lexer()
	{
		while(_first)
		{
			Token *next = _first->next;
			delete _first;
			_first = next;
		}
	}

	// Scans for the next token.
	bool Lexer::Scan()
	{
		const char *s = _script;
		if (_last)
		{
			s = _script + _last->start + _last->length;
			if (_last->type == Token::StringToken)
				s++;
		}

		static int keywordCount = sizeof(keywords) / sizeof(TokenDefinition);
		static int operatorCount = sizeof(operators) / sizeof(TokenDefinition);

		while(*s)
		{
			// skip whitespace
			while(*s && (*s == ' ' || *s == '	' || *s == '\r' || *s == '\n'))
			{
				++s;
			}

			// single line comment
			if (s[0] == '/' && s[1] == '/')
			{
				while (*s && *s != '\n')
				{
					s++;
				}
			}
		
			// multiline comment
			else if (s[0] == '/' && s[1] == '*')
			{
				s += 2;
				while (*s && s[0] != '*' && s[1] != '/')
				{
					++s;
				}
				s += 2;
			}

			// identifier
			else if (IsStartOfIdentifier(*s))
			{
				const char *id = s++;
				while(*s && IsIdentifierChar(*s))
					++s;
				if (!id[1] && *id == '_')
				{
					Throw(s - _script, "'_' cannot be used as an identifier.");
				}
				Token *t = AddToken(id, s - id, Token::IdentifierToken);
				if (t->length < 10)
				{
					char keyword[11];
					t->ToString(this, keyword);
					for(int i = 0; i < keywordCount; i++)
					{
						if (!strcmp(keyword, keywords[i].token))
						{
							t->type = Token::KeywordToken;
							t->identifier = keywords[i].id;
							break;
						}
					}
				}
				return true;
			}

			// number
			else if (IsNumeric(*s))
			{
				const char *number = s;
				if (*s == '0' && (s[1] == 'x' || s[1] == 'X'))
				{
					s += 2;
					while(*s && IsHexadecimal(*s))
					{
						++s;
					}
					if (s - number > 10)
					{
						Throw(s - _script, "Hexadecimal literal is too large.");
					}
					if (IsAlpha(*s) || *s == '_')
					{
						Throw(s - _script, "Invalid characters in hexadecimal literal.");
					}
					if (s - number == 2)
					{
						Throw(s - _script, "Expected hexadecimal literal.");
					}
					AddToken(number, s - number, Token::IntegerToken);
					return true;
				}

				else
				{
					s++;
					bool exp = false;
					bool expNum = false;
					bool dot = false;
					bool frac = false;
					bool expNeg = false;

					while (*s && (IsNumeric(*s) || *s == '.' || *s == 'e' || *s == 'E' || *s == '-'))
					{
						if (*s == '.')
						{
							if (dot || exp || expNeg)
								Throw(s - _script, "Number literal is invalid.");
							dot = true;
						}
						else if (*s == '-')
						{
							if (exp && !expNum)
								expNeg = true;
							else
								Throw(s - _script, "Number literal is invalid.");
						}
						else if (*s == 'e' || *s == 'E')
						{
							if (exp || (dot && !frac))
								Throw(s - _script, "Number literal is invalid.");
							exp = true;
						}
						else
						{
							if (dot && !exp)
								frac = true;
							else if (exp)
								expNum = true;
						}
						s++;
					}
					if ((dot && !frac) || (exp && !expNum))
						Throw(s - _script, "Number literal is invalid.");
					bool flt = frac || expNeg;
					AddToken(number, s - number, (flt ? Token::FloatToken : Token::IntegerToken));
					return true;
				}
			}

			// string literal
			else if (*s == '"' || *s == '\'')
			{
				const char *str = s++;
				while (*s && *s != *str)
				{
					if (*s == '\n')
						Throw(s - _script, "New-line found in string literal. Use escape sequence '\\n' for new lines.");
					if (*s == '\\')
					{
						++s;
						switch(*s)
						{
							case 'r':
							case 'n':
							case 'b':
							case 't':
							case '\'':
							case '"':
							case '0':
							case 'a':
							case 'f':
							case 'v':
								break;

							case 'x':
							{
								if (!IsHexadecimal(s[1]) || IsHexadecimal(s[2]))
									Throw(s - _script, "Hexadecimal character escape sequence '\\x%c%c' is invalid.", s[1], s[2]);
								s += 2;
								break;
							}

							default:
								Throw(s - _script, "Invalid character escape sequence found.");
						}
					}
					s++;
				}
				if (!*s)
					Throw(s - _script, "Unexpected end-of-script found.");
				AddToken(str + 1, s - str - 1, Token::StringToken);
				s++;
				return true;
			}

			// operator/punctuator
			else
			{
				const char *optr = s;
				for(int i = 0; i < operatorCount; i++)
				{
					TokenDefinition &def = operators[i];
					int len = strlen(def.token);
					if (!strncmp(optr, def.token, len))
					{
						Token *t = AddToken(optr, len, Token::OperatorToken);
						t->identifier = def.id;
						return true;
					}
				}
				Throw(s - _script, "illegal character(s) found.");
			}
		}

		return false;
	}

	// Scans the entire script.
	void Lexer::ScanAll()
	{
		while(Scan());
	}

	// Gets a pointer to the script text.
	const char *Lexer::GetScript()
	{
		return _script;
	}

	// Gets the first token in the script.
	Token *Lexer::GetFirst()
	{
		return _first;
	}

	// Gets the last token since scanning.
	Token *Lexer::GetLast()
	{
		return _last;
	}

	// Adds a token to the list.
	Token *Lexer::AddToken(const char *start, int length, Token::TokenType type)
	{
		Token *tok = new Token();
		tok->start = start - _script;
		tok->length = length;
		tok->type = type;
		tok->next = 0;
		if (!_first)
			_first = tok;
		else
			_last->next = tok;
		_last = tok;
		return tok;
	}

	// Gets the line and character index (one-based) for an index in the script.
	int Lexer::GetLineAndColumn(int index, int &column)
	{
		int line = 1;
		column = 0;
		for(int i = 0; i < index; i++)
			if (_script[i] == '\n')
				line++;
		for(int i = index; i >= 0 && _script[i] != '\n'; i--)
			++column;
		return line;
	}
	
	// Throws a formatted SyntaxErrorException.
	void Lexer::Throw(int index, const char *format, ...)
	{
		int column;
		int line = GetLineAndColumn(index, column);

		va_list args;
		va_start(args, format);
		char buffer[256];
		vsprintf_s(buffer, 256, format, args);

		throw SyntaxErrorException(line, column , _source, buffer);
	}
}